#pragma once
#include "dataManage.hpp"
#include "onlineManage.hpp"
#include "util.hpp"
#include <vector>
#include <mutex>
#include <string>
#include <memory>
#include <unordered_map>

// 房间类
#define BOARD_ROW 15
#define BOARD_COL 15
#define EMPTY 0
#define WHITE_CORLOR 1
#define BLACK_CORLOR 2

/*-------------------------------------房间类---------------------------------------------
作用：定义每个房间的属性，以及玩家可以做的动作，例如走棋、聊天、判断胜负等等*/
class room
{
public:
    bool place_chess(Json::Value &req)// 走棋动作
    {
        Json::Value  resp;// 预先定义一个Json格式的响应
        if(is_win == true)// 如果当前房间已经产生赢家
        {
            DEBUG_LOG("当前房间已有玩家胜利！不能继续走棋！");
            return false;
        }

        //1. 判断该用户是否在游戏房间房中
        uint64_t uid = req["uid"].asInt64();
        bool ret = _om->is_in_room(uid);
        if(ret == false)
        {
            if(uid == use_white_uid())// 当前用户不在游戏房间中并且是白棋玩家，获胜的就是黑棋玩家
            {
                resp["result"] = true;
                resp["winner"] = (Json::Int64)use_black_uid();
                _dm->user_win(use_black_uid());
                _dm->user_lose(uid);// 以上两句都是在更新数据库的信息
            }
            else if(uid == use_black_uid())// 当前用户不在游戏房间中并且是黑棋玩家，获胜的就是白棋玩家
            {
                resp["result"] = true;
                resp["winner"] = (Json::Int64)use_white_uid();
                _dm->user_win(use_white_uid());
                _dm->user_lose(uid);
            }
            resp["reason"] = "对方掉线！你赢得本场对局！";
            resp["room_id"] = (Json::Int64)use_room_id();
            resp["uid"] = (Json::Int64)uid;
            
            broadcast(resp);// 消息广播
            ERROR_LOG("下棋失败！原因：当前用户%d的用户不在游戏房间当中！",uid);
            return false;
        }

        //2. 判断该用户是白棋玩家还是黑棋玩家
        //  1. 判断走棋的位置是否存在棋子
        size_t row = req["row"].asInt64();// Json格式的请求当中包含下棋的位置(自定义协议)
        size_t col = req["col"].asInt64();
        if(_board[row][col] != EMPTY)
        {
            // for(int i=0;i<_board.size();i++)
            // {
            //     for(int j=0;j<_board[0].size();j++)
            //     {
            //         std::cout << _board[i][j] << " ";
            //     }
            //     std::cout << std::endl;
            // }
            ERROR_LOG("下棋失败！原因：下棋的位置已经有其他棋子！");
            resp["optype"] = "put_chess";
            resp["result"] = false;// 操作失败
            resp["reason"] = "下棋的位置已经有其他棋子！";
            resp["room_id"] = (Json::Int64)use_room_id();
            resp["uid"] = (Json::Int64)uid;
            broadcast(resp);
            return false;
        }
        //  2. 下棋 
        //      1. 判断胜负
        //      2. 广播消息
        if(uid == use_white_uid())// 白色棋子玩家走棋
        {
            _board[row][col] = WHITE_CORLOR;
            bool ret = judge_success(row,col,WHITE_CORLOR);// 每走一步判断一次游戏是否胜利
            if(ret == true)
            {
                resp["optype"] = "put_chess";
                resp["result"] = true;
                resp["reason"] = "你赢了！";
                resp["room_id"] = (Json::Int64)use_room_id();
                resp["uid"] = (Json::Int64)uid;
                resp["winner"] = (Json::Int64)use_white_uid();
                resp["row"] = (Json::Int64)row;
                resp["col"] = (Json::Int64)col;
                _dm->user_win(uid);
                _dm->user_lose(use_black_uid());// 以上两句在更新数据库
                broadcast(resp);
                is_win = true;// 记录当前房间已经有胜利玩家
                return true;
            }
            // 没有胜利
            resp["optype"] = "put_chess";
            resp["result"] = true;
            resp["room_id"] = (Json::Int64)use_room_id();
            resp["uid"] = (Json::Int64)uid;
            resp["winner"] = (Json::Int64)0;
            resp["row"] = (Json::Int64)row;
            resp["col"] = (Json::Int64)col;
            broadcast(resp);
            return true;
        }
        else if(uid == use_black_uid())// 同上
        {
            _board[row][col] = BLACK_CORLOR; 
            bool ret = judge_success(row,col,BLACK_CORLOR);
            if(ret == true)
            {
                resp["optype"] = "put_chess";
                resp["result"] = true;
                resp["reason"] = "你赢了！";
                resp["room_id"] = (Json::Int64)use_room_id();
                resp["uid"] = (Json::Int64)uid;
                resp["winner"] = (Json::Int64)use_black_uid();
                resp["row"] = (Json::Int64)row;
                resp["col"] = (Json::Int64)col;
                _dm->user_win(uid);
                _dm->user_lose(use_white_uid());
                broadcast(resp);
                is_win = true;
                return true;
            }
            resp["optype"] = "put_chess";
            resp["result"] = true;
            resp["room_id"] = (Json::Int64)use_room_id();
            resp["uid"] = (Json::Int64)uid;
            resp["winner"] = (Json::Int64)0;
            resp["row"] = (Json::Int64)row;
            resp["col"] = (Json::Int64)col;
            broadcast(resp);
            return true;
        }
        return false;
    }

    bool chat(Json::Value &req)// 聊天
    {
        Json::Value resp = req;
        resp["result"] = true;
        broadcast(resp);// 聊天就是对请求包进行一次转发
        return true;
    }

    bool exit_room(uint64_t uid)// 退出游戏房间
    {
        Json::Value resp;
        //1. 判断要退出的用户是否在游戏房间当中
        bool ret = _om->is_in_room(uid);
        if(ret == false)
        {
            ERROR_LOG("退出游戏房间失败！原因：该用户本身就不在游戏房间中！");
            return false;
        }
        //2. 判断退出的用户是黑棋还是白棋
        if(uid == use_white_uid())
        {
            resp["optype"] = "put_chess";
            resp["result"] = true;
            resp["reason"] = "对方退出！你赢得本场对局！";
            resp["room_id"] = (Json::Int64)use_room_id();
            resp["uid"] = (Json::Int64)uid;
            resp["winner"] = (Json::Int64)use_black_uid();
            resp["row"] = -1;
            resp["col"] = -1;
            broadcast(resp);
            // 如果房间没有赢家，可以改变积分
            if(!is_win)// 对方退出并且房间内没有赢家，则可以更新数据(否则可能会造成胜利者胜利两次的现象)
            {
                _dm->user_win(use_black_uid());
                _dm->user_lose(uid);
                is_win = true;
            }
            return true;
        }
        else if(uid == use_black_uid())
        {
            resp["optype"] = "put_chess";
            resp["result"] = true;
            resp["reason"] = "对方退出！你赢得本场对局！";
            resp["room_id"] = (Json::Int64)use_room_id();
            resp["uid"] = (Json::Int64)uid;
            resp["winner"] = (Json::Int64)use_white_uid();
            resp["row"] = -1;
            resp["col"] = -1;
            broadcast(resp);// uid的玩家退出了，能看到这条消息的肯定是另一个玩家啦
            // 如果房间没有赢家，可以改变积分
            if(!is_win)
            {
                _dm->user_win(use_white_uid());
                _dm->user_lose(uid);
                is_win = true;
            }
            return true;
        }
        //3. 减少游戏房间当中的玩家数量
        --_count;
        return true;
    }

    //4. 判断游戏胜负
    bool judge_success(int row,int col,int chess_corlor)
    {
        if(five(row,col,0,1,chess_corlor) ||/*横向*/
        five(row,col,1,0,chess_corlor) ||/*纵向*/
        five(row,col,1,1,chess_corlor) ||/*反斜*/
        five(row,col,1,-1,chess_corlor))/*正斜*/
        return true;// 任意有一方向五子连珠都属于游戏胜利
        return false;
    }

    void broadcast(Json::Value &resp)// 广播结果
    {
        //1. 获取白棋玩家和黑棋玩家的连接
        wsserver_t::connection_ptr white_user_conn = _om->get_room_connect(use_white_uid());
        wsserver_t::connection_ptr black_user_conn = _om->get_room_connect(use_black_uid());
        //2. 广播
        std::string body;
        json_util::serialize(resp,body);// 将string对象转换成Json格式
        if(white_user_conn.get() != nullptr)// 连接存在就发送消息
        {
            white_user_conn->send(body);
        }
        if(black_user_conn.get() != nullptr)// 连接存在就发送消息
        {
            black_user_conn->send(body);
        }
    }

    bool five(int row,int col,int offset_row,int offset_col,int chess_corlor)//7. 五子连珠判断
    {
        int cur_row = row + offset_row;
        int cur_col = col + offset_col;
        int count = 1;
        // 正偏移量判断和反偏移量判断
        while(cur_row >= 0 && cur_row < BOARD_ROW && cur_col >= 0 && cur_col < BOARD_COL && _board[cur_row][cur_col] == chess_corlor)
        {
            cur_row += offset_row;
            cur_col += offset_col;
            ++count;
        }
        cur_row = row - offset_row;
        cur_col = col - offset_col;
        while(cur_row >= 0 && cur_row < BOARD_ROW && cur_col >= 0 && cur_col < BOARD_COL && _board[cur_row][cur_col] == chess_corlor)
        {
            cur_row -= offset_row;
            cur_col -= offset_col;
            ++count;
        }
        return count >= 5;
    }
public:
    room(data_manager *dm,online_manager *om)
        :_dm(dm),_om(om),_board(BOARD_ROW,std::vector<int>(BOARD_COL,EMPTY)),_count(0)
    {}
    //5. 统一接受请求接口，根据不同的请求调用不同的成员函数
    bool handle_request(Json::Value &req)
    {
        //1. 判断请求字段是否为空
        if(req["optype"].isNull())
        {
            ERROR_LOG("请求房间管理模块功能错误！原因:传入的json对象没有optype选项！");
            return false;
        }
        //2. 根据不同的操作类型调用不同的成员函数
        std::string optype_str = req["optype"].asString();
        if(optype_str == "put_chess")
        {
            place_chess(req);// 用户id为uid的用户请求下棋
            return true;
        }
        else if(optype_str == "chat")
        {
            chat(req);// 调用聊天动作
            return true;
        }
    }
    
    void set_room_id(size_t id) {_room_id = id;}// 设置房间id
    void set_white_uid(size_t id) {_white_uid = id;}// 设置白棋玩家id
    void set_black_uid(size_t id) {_black_uid = id;}// 设置黑棋玩家id
    size_t use_room_id() {return _room_id;}// 获取房间id
    size_t use_white_uid() {return _white_uid;}// 获取白棋玩家id
    size_t use_black_uid() {return _black_uid;}// 获取黑棋玩家id
    void add_room_user_count() {++_count;}// 添加房间玩家数
    size_t use_user_count() {return _count;}// 获取房间玩家数
protected:
    data_manager *_dm;// 需要用户对应的数据
    online_manager *_om;// 需要用户的在线情况，主要是得到用户对应的连接
    // 用户需要下棋，所以需要一个棋盘
    std::vector<std::vector<int>> _board;// 0:没有棋子；1：白色棋子；2：黑色棋子
    //4. 保证线程安全
    //std::mutex _mutex;
    size_t _room_id;// 房间号
    size_t _white_uid;
    size_t _black_uid;// 白棋玩家id和黑棋玩家id
    size_t _count;// 游戏房间当中的玩家数量
    bool is_win = false;// 游戏房间是否产生赢家
};

/*--------------------------------------房间管理类------------------------------------------------
作用：管理房间类，进行一个房间的创建、销毁、调度工作*/
typedef std::shared_ptr<room> room_t;
class room_manager
{
public:
    room_manager(data_manager *dm,online_manager *om) 
        : _room_id(1),_dm(dm),_om(om)// 房间id从1开始
    {}

    //1. 为两个玩家创建一个房间
    bool create_room(uint64_t uid1,uint64_t uid2)
    {
        std::unique_lock<std::mutex> lock(_mutex);// 加锁
        if(_om->is_in_hall(uid1) && _om->is_in_hall(uid2))// 如果这两个玩家都在游戏房间中就分配房间
        {
            //2. 分配房间
            std::shared_ptr<room> room_sp(new room(_dm,_om));// new一个房间
            room_sp->set_room_id(_room_id);// 设置房间号
            room_sp->set_white_uid(uid1);// 设置白色棋子玩家
            room_sp->add_room_user_count();// 添加房间人数
            room_sp->set_black_uid(uid2);// 设置黑色棋子玩家
            room_sp->add_room_user_count();// 添加房间人数

            _roomId_room_t.insert(std::make_pair(_room_id,room_sp));// 放到哈希表中进行管理
            _uid_room_t.insert(std::make_pair(uid1,_room_id));
            _uid_room_t.insert(std::make_pair(uid2,_room_id));// 玩家1和玩家2和房间进行一个关联
            ++_room_id;// ++房间号
            return true;
        }
        ERROR_LOG("分配房间错误!两名玩家不都在游戏大厅中！");
        return false;
    }

    bool exit_room(uint64_t uid)// 退出游戏房间
    {
        //1. 判断该用户是否在游戏房间
        bool ret = _om->is_in_room(uid);
        if(ret == false)
        {
            ERROR_LOG("该用户无法退出！原因:该用户本身就不在游戏房间中！");
            return false;
        }
        //2. 从房间当中删除该用户
        //  1.获得房间id
        size_t room_id = find_by_uid(uid);
        if(room_id == -1)
        {
            ERROR_LOG("该用户无法退出！原因:无法获得该用户对应的房间id！");
            return false;
        }
        room_t room_ptr = find_by_room_id(room_id);//房间类当中定义了退出房间的动作，所以需要玩家对应房间的句柄
        if(room_ptr.get() == nullptr)
        {
            ERROR_LOG("该用户无法退出！原因:无法获得该用户对应的房间句柄！");
            return false;
        }
        
        std::unique_lock<std::mutex> lock(_mutex);// 执行退出动作之前进行一个加锁
        ret = room_ptr->exit_room(uid);// 退出
        if(ret == false)
        {
            return false;// 退出房间失败
        }
        _uid_room_t.erase(uid);// 删除某个玩家和玩家的对应关系
        //3. 判断房间是否需要销毁
        romove_room(room_ptr,room_id);
        return true;//退出游戏房间成功
    }

    room_t find_by_room_id(size_t room_id)// 根据房间id查找房间
    {
        std::unique_lock<std::mutex> lock(_mutex);// 加锁
        auto room_ptr_it = _roomId_room_t.find(room_id);
        if(room_ptr_it == _roomId_room_t.end())
        {
            ERROR_LOG("无法查找房间！原因:无法获得该用户对应的房间句柄！");
            return room_t();
        }
        return room_ptr_it->second;
    }

    size_t find_by_uid(uint64_t uid)// 根据用户id查找房间
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto room_id_it = _uid_room_t.find(uid);
        if(room_id_it == _uid_room_t.end())
        {
            ERROR_LOG("该用户无法退出！原因:无法获得该用户对应的房间id！");
            return -1;
        }
        return room_id_it->second;
    }
protected:
    void romove_room(room_t room_ptr,uint64_t room_id)// 当游戏房间没有玩家时需要删除房间
    {   
        //1. 判断房间剩余的玩家人数
        if(room_ptr->use_user_count() > 0)
        {
            ERROR_LOG("无法销毁房间！原因：当前房间还有玩家！");
            return;
        }
        //2. 销毁房间
        _roomId_room_t.erase(room_id);// 删除房间id和房间对象的映射！因为哈希表存的是智能指针，所以引用计数为0的时候自动销毁！
    }
protected:
    size_t _room_id;// 为每一个房间分配房间id
    std::mutex _mutex;// 保证线程安全
    std::unordered_map<size_t,room_t> _roomId_room_t;// 需要保存每一个房间id到房间的映射
    std::unordered_map<uint64_t,size_t> _uid_room_t;//4. 需要保存每一个用户id到房间id的映射
    data_manager *_dm;// 用户相关的数据
    online_manager *_om;// 在线用户管理句柄:获得用户对应的连接
};






